package com.hisense.ovcloud.apicontrol.service;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.hisense.ovcloud.apicontrol.config.Constants;
import com.hisense.ovcloud.apicontrol.config.WorkPoolProperties;
import com.hisense.ovcloud.apicontrol.dto.req.EncryptReq;
import com.hisense.ovcloud.apicontrol.dto.req.TokenReq;
import com.hisense.ovcloud.apicontrol.dto.req.ValidateReq;
import com.hisense.ovcloud.apicontrol.dto.resp.EncryptRes;
import com.hisense.ovcloud.apicontrol.dto.resp.TokenRes;
import com.hisense.ovcloud.apicontrol.dto.resp.ValidateResp;
import com.hisense.ovcloud.apicontrol.errors.ResForGW;
import com.hisense.ovcloud.apicontrol.errors.SystemError;
import com.hisense.ovcloud.apicontrol.tool.PathUtils;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

import static com.hisense.ovcloud.apicontrol.config.Constants.PARAM_SIGN;
import static com.hisense.ovcloud.apicontrol.config.Constants.SecurityParamters.SECURITY_DATA_PREFIX;
import static com.hisense.ovcloud.apicontrol.config.Constants.SecurityParamters.SECURITY_START_VER;
import static com.hisense.ovcloud.apicontrol.tool.PathUtils.ifInEndPoints;

@Service
@Slf4j
public class GatewayMainService implements InitializingBean {
    private final PuidCheckService puidCheckService;

    private final VerifySignatureService verifySignatureService;

    private final JHKSyncService jhkSyncService;

    private final AccessTokenCheckService accessTokenCheckService;

    private final ApplianceTokenCheckService applianceTokenCheckService;

    private static Scheduler workPool;

    private final WorkPoolProperties workPoolProperties;

    private final TokenAuthService tokenAuthService;

    @Value("${request.decrypt.endpoints:aaa}")
    private String requestDecryptEndPoints;

    @Value("${encryptedBodyDisplay:true}")
    private boolean encryptedBodyDisplay;

    //忽略返回值签名path列表
    @Value("${ignore.upstream.signature.endpoints:/screen-appliance/,/internal/wim}")
    private String ignoreUpSignEndpoint;

    //返回值加密path列表
    @Value("${response.encrypt.endpoints:aaa}")
    private String responseEncryptEndPoints;

    private final ApiControlService apiControlService;

    public static final ObjectMapper mapper = new ObjectMapper();

    @PostConstruct
    public void init() {
        mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

        mapper.enable(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY);

        // 自定义 Number 反序列化器（强制转为 Double）
        SimpleModule numberModule = new SimpleModule();
        numberModule.addDeserializer(Number.class, new JsonDeserializer<Number>() {
            @Override
            public Number deserialize(JsonParser p, DeserializationContext ctxt)
                    throws IOException {
                JsonToken token = p.getCurrentToken();
                if (token == JsonToken.VALUE_NUMBER_INT) {
                    return p.getNumberValue();
                } else if (token == JsonToken.VALUE_NUMBER_FLOAT) {
                    return p.getDoubleValue();
                }
                throw new IllegalArgumentException("Unexpected token type: " + token);
            }
        });
        // 保留 Map 顺序和嵌套结构
        SimpleModule mapModule = new SimpleModule();
        mapModule.addAbstractTypeMapping(Map.class, LinkedHashMap.class);
        mapper.registerModule(mapModule);
        mapper.registerModule(numberModule); // 注册模块
    }

    public GatewayMainService(ApiControlService apiControlService, PuidCheckService puidCheckService, VerifySignatureService verifySignatureService,
                              JHKSyncService jhkSyncService, AccessTokenCheckService accessTokenCheckService, ApplianceTokenCheckService applianceTokenCheckService, WorkPoolProperties workPoolProperties, TokenAuthService tokenAuthService) {
        this.apiControlService = apiControlService;
        this.puidCheckService = puidCheckService;
        this.verifySignatureService = verifySignatureService;
        this.jhkSyncService = jhkSyncService;
        this.accessTokenCheckService = accessTokenCheckService;
        this.applianceTokenCheckService = applianceTokenCheckService;
        this.workPoolProperties = workPoolProperties;
        this.tokenAuthService = tokenAuthService;
    }

    public Mono<ValidateResp> validate(Mono<ValidateReq> req) {
        return req.flatMap(r -> {
                    ValidateResp res = ValidateResp.builder().build();
                    boolean isSign = isSign(r);
                    return parseParameters(r)
                            .zipWhen(parametersMap ->
                                            Mono.zip(
                                                    Mono.just(checkReqCrypt(r, parametersMap)),
                                                    puidCheckService.run(r, parametersMap)
                                            )
                                            .zipWhen(pair1 -> {
                                                if (!pair1.getT1()) {
                                                    return Mono.just(errorReturnSysError(SystemError.REQUEST_MUST_CRYPT, isSign));
                                                }

                                                if (pair1.getT2().isStopForwarding()) {
                                                    log.error("puidCheckService error: {}", Optional.ofNullable(pair1.getT2().getDesc()).orElse(""));
                                                    return Mono.just(errorReturn(pair1.getT2(), isSign));
                                                }

                                                return Mono.zip(
                                                        jhkSyncService.run(r, parametersMap),
                                                        verifySignatureService.run(r, parametersMap)
                                                ).zipWhen(pair -> {
                                                    if (pair.getT1().isStopForwarding()) {
                                                        log.error("jhk sync error: {}", Optional.ofNullable(pair.getT1().getDesc()).orElse(""));
                                                        return Mono.just(errorReturn(pair.getT1(), isSign));
                                                    }

                                                    res.setJhkSync((boolean)(pair.getT1().getData()));

                                                    if (verifySignatureService.shouldFilter(r)) {
                                                        if (pair.getT2().isStopForwarding()) {
                                                            log.error("verifySignature error: {}", Optional.ofNullable(pair.getT2().getDesc()).orElse(""));
                                                            return Mono.just(errorReturn(pair.getT2(), isSign));
                                                        }
                                                    }
                                                    return Mono.zip(
                                                            accessTokenCheckService.run(r, parametersMap),
                                                            applianceTokenCheckService.run(r, parametersMap)
                                                    ).zipWhen(tup -> {
                                                        if (accessTokenCheckService.shouldFilter(r)) {
                                                            if (tup.getT1().isStopForwarding()) {
                                                                log.error("accessTokenCheck error: {}", Optional.ofNullable(pair.getT1().getDesc()).orElse(""));
                                                                return Mono.just(errorReturn(tup.getT1(), isSign));
                                                            }
                                                            res.setInsertMaps((Map<String, Object>) (tup.getT1().getData()));
                                                        }
                                                        if (applianceTokenCheckService.shouldFilter(r)) {
                                                            if (tup.getT2().isStopForwarding()) {
                                                                log.error("applianceTokenCheck error: {}", Optional.ofNullable(pair.getT2().getDesc()).orElse(""));
                                                                return Mono.just(errorReturn(tup.getT2(), isSign));
                                                            }
                                                            res.setInsertMaps((Map<String, Object>) (tup.getT2().getData()));
                                                        }

                                                        res.setRespEncrypt(isRespEncrypt(r, parametersMap));
                                                        res.setRespSign(isSign);
                                                        res.setValid(true);

                                                        if (checkIfEncryptedBody(r)) {
                                                            res.setNeedDecrypt(true);
                                                            if (res.getInsertMaps() != null && !res.getInsertMaps().isEmpty()) {
                                                                parametersMap.putAll(res.getInsertMaps());
                                                            }
                                                            res.setDecryptedBody(parametersMap);
                                                        }
                                                        return Mono.just(res);
                                                    }, (x, y) -> y);
                                                }, (x, y) -> y);
                                            }, (x, y) -> y)
                                    , (x, y) -> y);
                })
                .subscribeOn(workPool);
    }

    private boolean checkIfEncryptedBody(ValidateReq r) {
        return r.getMethod().equalsIgnoreCase(Constants.Method.POST) && StringUtils.isNotEmpty(r.getBody()) && r.getBody().startsWith(SECURITY_DATA_PREFIX);
    }

    public Mono<EncryptRes> encrypt(Mono<EncryptReq> req) {
        return req.flatMap(r -> apiControlService.encrypt(r.getPlaintxt())
                        .zipWhen(encryptedStr -> Mono.just(EncryptRes.builder().encryptedStr(encryptedStr).build())
                                , (x, y) -> y))
                .onErrorReturn(EncryptRes.builder().build())
                .subscribeOn(workPool);
    }


    public Mono<TokenRes> tokenCheck(Mono<TokenReq> req) {
        return req.flatMap(r -> tokenAuthService.tokenAuth(r.getPath(), r.getAccessToken()))
                .subscribeOn(workPool);
    }

    private ValidateResp errorReturn(ResForGW lastRes, boolean isSign) {
        return ValidateResp.builder()
                .errorCode(lastRes.getResultCode())
                .errorMsg(lastRes.getDesc())
                .isRespSign(isSign)
                .build();
    }
    private ValidateResp errorReturnSysError(SystemError sys, boolean isSign) {
        return ValidateResp.builder()
                .errorCode(sys.value())
                .errorMsg(sys.desc())
                .isRespSign(isSign)
                .build();
    }

    @SneakyThrows
    public Mono<LinkedHashMap<String, Object>> parseParameters(ValidateReq req) {
        LinkedHashMap<String, Object> resp = new LinkedHashMap<>();
        if (req.getMethod().equalsIgnoreCase(Constants.Method.GET)) {
            if (StringUtils.isNotEmpty(req.getOrderedParams())) {
                String param = req.getOrderedParams();
                if (param.contains("&")) {
                    Arrays.asList(param.split("&")).forEach(x -> {
                        String[] keyValue = x.split("=");
                        if(keyValue.length >= 2){
                            resp.put(keyValue[0], x.substring(keyValue[0].length() + 1));
                        }
                    });
                } else if (param.contains("=")) {
                    String[] keyValue = param.split("=");
                    if(keyValue.length >= 2){
                        resp.put(keyValue[0], param.substring(keyValue[0].length() + 1));
                    }
                }
            }
            log.debug("parseParameters get resp: {}", JSONUtil.toJsonStr(resp));
            return Mono.just(resp);
        } else if (req.getMethod().equalsIgnoreCase(Constants.Method.POST)) {
            if (StringUtils.isNotEmpty(req.getBody())) {
                if (req.getBody().startsWith(SECURITY_DATA_PREFIX) && req.getBody().length() > 5) {
                    return apiControlService.decrypt(req.getBody().substring(5))
                            .flatMap(decryptedBody -> {
                                try {
                                    if (encryptedBodyDisplay) {
                                        log.debug("encrypted body: {}", decryptedBody);
                                    }
                                    return Mono.just(transferBodyToOriginSortMap(decryptedBody));
                                } catch (JsonProcessingException e) {
                                    return Mono.just(new LinkedHashMap<>());
                                }
                            });
                } else {
                    log.debug("requestBody: {}", req.getBody());
                    LinkedHashMap<String, Object> res = new LinkedHashMap<>();
                    if(StringUtils.isNotEmpty(req.getBody())){
                        res = transferBodyToOriginSortMap(req.getBody());
                    }
                    return Mono.just(res);
                }
            }
        }

        return Mono.just(resp);
    }

    public static LinkedHashMap<String, Object> staticParseParameters(ValidateReq req) {
        LinkedHashMap<String, Object> resp = new LinkedHashMap<>();
        if (req.getMethod().equalsIgnoreCase(Constants.Method.GET)) {
            if (StringUtils.isNotEmpty(req.getOrderedParams())) {
                String param = req.getOrderedParams();
                if (param.contains("&")) {
                    Arrays.asList(param.split("&")).forEach(x -> {
                        if(x.startsWith("sign=")){
                            if(x.length() > 5){
                                resp.put(PARAM_SIGN, x.substring(5));
                            }
                        }else{
                            String[] keyValue = x.split("=");
                            resp.put(keyValue[0], keyValue[1]);
                        }
                    });
                } else if (param.contains("=")) {
                    String[] keyValue = param.split("=");
                    if(keyValue.length == 2){
                        resp.put(keyValue[0], keyValue[1]);
                    }
                }
            }
            log.debug("parseParameters get resp: {}", JSONUtil.toJsonStr(resp));
            return resp;
        }

        return resp;
    }

    private boolean isSign(ValidateReq req) {
        return Arrays.stream(ignoreUpSignEndpoint.split(",")).noneMatch(req.getPath()::contains);
    }

    private boolean isRespEncrypt(ValidateReq req, LinkedHashMap<String, Object> parametersMap) {
        if (req.getMethod().equalsIgnoreCase(Constants.Method.GET) || req.getMethod().equalsIgnoreCase(Constants.Method.POST)) {
            if(parametersMap.containsKey(Constants.VERSION)){
                double version = Double.parseDouble(Optional.ofNullable(parametersMap.get(Constants.VERSION).toString()).orElse("0.0"));
                if (StringUtils.isEmpty(req.getContentType())) {
                    req.setContentType("application/json");
                }
                if (req.getContentType().contains("application/json")) {
                    if (version >= SECURITY_START_VER) {
                        return PathUtils.ifInEndPoints(req.getPath(), responseEncryptEndPoints);
                    }
                }
            }
        }
        return false;
    }

    private boolean checkReqCrypt(ValidateReq r, LinkedHashMap<String, Object> parametersMap) {
        if (ifInEndPoints(r.getPath(), requestDecryptEndPoints)) {
            double apiVersion = 0.0;
            if (parametersMap.containsKey(Constants.VERSION)) {
                apiVersion = Double.parseDouble(Optional.ofNullable(parametersMap.get(Constants.VERSION).toString()).orElse("0.0"));
            }

            if(apiVersion >= SECURITY_START_VER){
                if (r.getMethod().equalsIgnoreCase(Constants.Method.GET)) {
                    return false;
                } else if (r.getMethod().equalsIgnoreCase(Constants.Method.POST)){
                    return Optional.ofNullable(r.getBody()).orElse("").startsWith("HENC-");
                }
            }
        }
        return true;
    }

    private LinkedHashMap<String, Object> transferBodyToOriginSortMap(String body) throws JsonProcessingException {
        return mapper.readValue(
                body,
                mapper.getTypeFactory().constructType(LinkedHashMap.class)
        );
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        workPool = Schedulers.newBoundedElastic(workPoolProperties.getCap(), workPoolProperties.getQueueLength(), "edwin-work-pool");
    }

    public static void main(String[] args) {
        System.out.println(
                JSONUtil.toJsonPrettyStr(staticParseParameters(
                        ValidateReq.builder()
                                .path("/dms/get_customer_device_list_info")
                                .accessTokenInHeader("")
                                .method("GET")
                                .contentType("aaa")
                                .orderedParams("accessToken=ta1SQFGCpIE-_ZFaGjzlw2GhgDsT34bewAPFQUAfa5Vz8EdoLWABMKGDHrkvXYaqH86dNRsrs4909TtsLdB1ijjIlKb_5erc83Ae_-cFR3hGKojgV4YH8AExEs-y0iFMMRdTTHWt0j1AqCBcN3vkQ9FtDlZae8mrm-20XB4B6IQM79nL6tElmAa4sVHM952-Xx4EmyUJrBOsdJdqY0KptPRNYqw8iU9GFYYQ8ytlc7h6hBBtZ3TzQJyOeHJUOfAchkzrr5ngyMgBejYRKoDRnNhbGda_KScF6keOs5rqts4bCMEH9eW49-Ovj0eYfqGEgeHD6BHQeOzPlMizPZuBOtWP_CqMfWSuky_agoTytEsIYsc0ZqSfIDBNTp9ANHk-x2D_rmVJ4XmIJkwJSS7zbc9mwu_jlzWBzDcrnx1TPcBqlA72sUv3SZEasKVpyAQuJ3o&languageId=1&randStr=cd6ccf8b-5363-485b-a8bd-02d7ddceabbd1747118574615&sign=AqgQqnYKNPuXm3TJi-y-iPHBpYFhgSF_oUz5IcJlIiKI7kQBfi-P-vKsXiN0J5QIbWe4ZHbEzhw_\\nH4cWcx-R6N_bevmsNH_t1cEpaLdVtcemlmx0A3s1RqGplB3UsaqeubCegeHZeqf7w-9d6eS__cS6\\nufm7R_-S6VFGnGH4W5kq698tmxteC-sdnATfwjtzkiVVPeLdaRIaSTUANMgz5DrHEcXaUYiCraaC\\njZeipX-ityEvYSN9nDVauAKOeJbm6Rzo8umNc9ErWmMbO2PmdJpwUAgT3DEqgDWl712AIomgHwQt\\ngOT3OVdjp3HcCCfRKIxXzBsedTe3YB-rxjux3w==\\n&timeStamp=1747118574616&timezone=8&version=6.0")
                                .build()
                ))
        );
    }
}












































